﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Reflection.Emit;

namespace Ganaye.EmbeddedDb
{
    public delegate void QuickSaveToBytesDelegate(Record record);
        
    public abstract class TableColumn
    {
        protected int mOffset;
        protected int mNextColumnOffset;
        protected int mSize;
        protected int mDecimals;
        protected int mColumnNo;
        protected bool mIsNullable;
        protected Table mTable;
        internal ColumnType mColumnType;
        protected string mName;
        protected List<Index> mIndexes;
        protected static QuickValueParser mValueParser = new QuickValueParser();
        internal PropertyInfo mPropertyInfo;

        internal QuickSaveToBytesDelegate mQuickSaveToBytes;
        
        public int Size
        {
            get { return mSize; }
        }

        public string Name
        {
            get { return mName; }
        }

        public static TableColumn GetNew(
            Table table, 
            int columnNo,
            int offset,
            ColumnDefinition cd)
        {
            TableColumn newColumn = null;
            ColumnType columnType = cd.ColumnType;
            Type fieldType = cd.FieldType;
            int size = cd.Size;
            int decimals = cd.Decimals;

            switch (columnType)
            {
                case ColumnType.DBASE3_CHARACTER:
                    if (fieldType == typeof(string)) newColumn = new TableColumnString();
                    break;
                case ColumnType.DBASE3_DATE:
                    if (fieldType == typeof(DateTime)) newColumn = new TableColumnDateTime();
                    else if (fieldType == typeof(DateTime?)) newColumn = new TableColumnNullableDateTime();
                    break;
                case ColumnType.DBASE3_NUMBER:
                    if (fieldType == typeof(Double)) newColumn = new TableColumnDouble();
                    else if (fieldType == typeof(Double?)) newColumn = new TableColumnNullableDouble();
                    else if (fieldType == typeof(int)) newColumn = new TableColumnInt32();
                    else if (fieldType == typeof(int?)) newColumn = new TableColumnNullableInt32();
                    break;
                case ColumnType.DBASE3_LOGICAL:
                    if (fieldType == typeof(bool)) newColumn = new TableColumnBoolean();
                    else if (fieldType == typeof(bool?)) newColumn = new TableColumnNullableBoolean();
                    break;
            }
            if (newColumn == null)
                throw new RuntimeException(string.Format("Invalid combination {0} {1}", columnType.ToString(), fieldType.Name));

            newColumn.mTable = table;
            newColumn.mColumnNo = columnNo;
            newColumn.mName = cd.ColumnName;
            newColumn.mOffset = offset;
            newColumn.mSize = size;
            newColumn.mNextColumnOffset = offset + size;
            newColumn.mColumnType = columnType;
            newColumn.mDecimals = decimals;

            var mi_Write_Column = table.mProxyType.GetMethod("write_" + cd.ColumnName);


            var mi_Write_Column_Delegate = Delegate.CreateDelegate(
                typeof(QuickSaveToBytesDelegate),
                mi_Write_Column);
            
            newColumn.mQuickSaveToBytes =
                (QuickSaveToBytesDelegate)mi_Write_Column_Delegate;

            return newColumn;
        }

        public virtual void Convert(ref object value)
        {
        }

        internal virtual void SlowSetValue(Record record, object value)
        {
            mPropertyInfo.SetValue(record, value, null);
        }

        internal virtual object SlowGetValue(Record record)
        {
            return mPropertyInfo.GetValue(record, null);
        }
    }

    public abstract class TableColumn<T> : TableColumn
    {
        protected abstract void InternalReadBufferValue(Record record, ref T currentValue);
        protected abstract int InternalCompare(T value1, T value2);
        
        public virtual void WriteValueInBuffer(Record record, T currentValue)
        {
        }

        public void ReadBufferValue(Record record, ref T currentValue)
        {
            if (record.mFieldStates[mColumnNo] == FieldState.None)
            {
                mValueParser.Init(record.GetReadBuffer(), this.mOffset, this.mNextColumnOffset);
                InternalReadBufferValue(record, ref currentValue);
                record.mFieldStates[mColumnNo] = FieldState.Read;
            }
        }

        public void ChangeCurrentValue(Record record, ref T currentValue, T newValue)
        {
            if (record.mPendingUpdateCount == 0)
            {
                using (record.BeginUpdates())
                {
                    ChangeCurrentValue(record, ref currentValue, newValue);
                }
            }
            else
            {
                if (record.mFieldStates[mColumnNo] == FieldState.None)
                {
                    byte[] buff=record.GetReadBuffer();
                    mValueParser.Init(buff, this.mOffset, this.mNextColumnOffset);
                    InternalReadBufferValue(record, ref currentValue);
                }
                if (InternalCompare(currentValue, newValue) == 0)
                {
                    record.mFieldStates[mColumnNo] = FieldState.Read;
                }
                else
                {
                    //todo: check indexes here            
                    currentValue = newValue;
                    record.mFieldStates[mColumnNo] = FieldState.Modified;
                }

            }
        }


    }
    
    class TableColumnString : TableColumn<String>
    {
        protected override void InternalReadBufferValue(Record record, ref string currentValue)
        {
            IHasEncoding encoding = mTable;
            byte[] buff=record.GetReadBuffer();
            if (buff[0] == 0)
            {
               // speeds up a lot if the record is blank
               currentValue = null;
            }
            else
            {
                currentValue = encoding.Encoding.GetString(buff, mOffset, mSize).Trim();
            }
        }

        protected override int InternalCompare(string value1, string value2)
        {
            return mTable.StringCompare(value1, value2);
        }

        public override void WriteValueInBuffer(Record record, string currentValue)
        {
            IHasEncoding encoding = mTable;
            Byte[] bytes=encoding.Encoding.GetBytes(currentValue);
            Byte[] buff = record.GetReadBuffer();
            if (bytes.Length >= mSize)
            {
                Array.Copy(bytes, 0, buff, mOffset, mSize);
            }
            else
            {
                Array.Copy(bytes, 0, buff, mOffset, bytes.Length);
            }
        }
    }

    abstract class ComparableTableColumn<T> : TableColumn<T>
        where T : IComparable<T>
    {
        protected override int InternalCompare(T value1, T value2)
        {
            return value1.CompareTo(value2);
        }
    }

    class TableColumnInt32 : ComparableTableColumn<Int32>
    {
        protected override void InternalReadBufferValue(Record record, ref int currentValue)
        {
            int? newValue = mValueParser.ParseNullableInt(mTable.mDecimalSeparator);
            currentValue = newValue.GetValueOrDefault();
        }


        public override void WriteValueInBuffer(Record record, int currentValue)
        {
            throw new NotImplementedException();
        }
    }

    class TableColumnDouble : ComparableTableColumn<Double>
    {
        protected override void InternalReadBufferValue(Record record, ref double currentValue)
        {
            var newValue = mValueParser.ParseNullableDouble(mTable.mDecimalSeparator);
            currentValue = newValue.GetValueOrDefault();
        }

        public override void WriteValueInBuffer(Record record, double currentValue)
        {
            throw new NotImplementedException();
        }
    }

    class TableColumnBoolean : ComparableTableColumn<Boolean>
    {
        protected override void InternalReadBufferValue(Record record, ref bool currentValue)
        {
            throw new NotImplementedException();
        }

        public override void WriteValueInBuffer(Record record, bool currentValue)
        {
            throw new NotImplementedException();
        }
    }

    class TableColumnDateTime : ComparableTableColumn<DateTime>
    {
        protected override void InternalReadBufferValue(Record record, ref DateTime currentValue)
        {
            var newValue = mValueParser.ParseNullableDate();
            currentValue = newValue.GetValueOrDefault();
        }

        public override void WriteValueInBuffer(Record record, DateTime currentValue)
        {
            int d = currentValue.Day;
            int m = currentValue.Month;
            int y = currentValue.Year;
            Byte[] buff = record.GetReadBuffer();
            int offset = mOffset;
            buff[offset++] = (byte)(((y / 1000) % 10) + '0');
            buff[offset++] = (byte)(((y / 100) % 10) + '0');
            buff[offset++] = (byte)(((y / 10) % 10) + '0');
            buff[offset++] = (byte)((y % 10) + '0');
            buff[offset++] = (byte)(((m / 10) % 10) + '0');
            buff[offset++] = (byte)((m % 10) + '0');
            buff[offset++] = (byte)(((d / 10) % 10) + '0');
            buff[offset++] = (byte)((d % 10) + '0');
        }
    }

    abstract class NullableTableColumn<T> : TableColumn<Nullable<T>>
        where T : struct, IComparable<T> 
    {
        override internal void SlowSetValue(Record record, object value)
        {
            Nullable<T> newValue = (Nullable<T>)(value);
            mPropertyInfo.SetValue(record, newValue, null);
        }

        protected override int InternalCompare(T? value1, T? value2)
        {
            if (value1.HasValue)
            {
                if (value2.HasValue)
                {
                    return value1.Value.CompareTo(value2.Value);
                }
                else return -1;
            }
            else return (value2.HasValue ? 1 : 0);
        }
    }


    class TableColumnNullableInt32 : NullableTableColumn<Int32>
    {
        public TableColumnNullableInt32()
        {
            throw new NotImplementedException();
        }

        protected override void InternalReadBufferValue(Record record, ref int? currentValue)
        {
            throw new NotImplementedException();
        }

        public override void WriteValueInBuffer(Record record, int? currentValue)
        {
            throw new NotImplementedException();
        }
    }

    class TableColumnNullableDouble : NullableTableColumn<Double>
    {
        protected override void InternalReadBufferValue(Record record, ref double? currentValue)
        {
            currentValue = mValueParser.ParseNullableDouble(this.mTable.mDecimalSeparator);
        }

        public override void WriteValueInBuffer(Record record, double? currentValue)
        {
            throw new NotImplementedException();
        }
    }

    class TableColumnNullableBoolean : NullableTableColumn<Boolean>
    {
        protected override void InternalReadBufferValue(Record record, ref bool? currentValue)
        {
            currentValue = mValueParser.ParseNullableBool();
        }

        public override void WriteValueInBuffer(Record record, bool? currentValue)
        {
            throw new NotImplementedException();
        }
    }

    class TableColumnNullableDateTime : NullableTableColumn<DateTime>
    {
        protected override void InternalReadBufferValue(Record record, ref DateTime? currentValue)
        {
            currentValue = mValueParser.ParseNullableDate();
        }

        public override void WriteValueInBuffer(Record record, DateTime? currentValue)
        {
            throw new NotImplementedException();
        }
    }

}
